package com.blb.edusearchservice.dao;

import com.alibaba.fastjson.JSON;
import com.blb.common.entity.PageEntity;
import com.blb.edusearchservice.entity.ElasticsearchEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * ES索引的操作类
 */
@Slf4j
@Repository
public class ElasticsearchDAO {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 判断索引存在
     * @param indexName
     * @return
     * @throws IOException
     */
    public boolean existIndex(String indexName) throws IOException {
        //创建索引查询的请求
        GetIndexRequest request = new GetIndexRequest(indexName);
        //判断索引是否存在
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        log.info("{}是否存在{}",indexName,exists);
        return exists;
    }

    /**
     * 删除索引
     * @param indexName
     * @throws IOException
     */
    public void deleteIndex(String indexName) throws IOException {
        //删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest(indexName);
        //发送删除请求
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        log.info("{}删除成功",indexName);
    }

    /**
     * 创建索引
     * @param indexName
     * @throws IOException
     */
    public void createIndex(String indexName) throws IOException {
        //创建索引请求
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        //发送创建索引请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        log.info("{}创建成功");
    }

    /**
     * 批量插入
     * @param indexName
     * @param list
     * @throws IOException
     */
    public void insertBatch(String indexName, List<ElasticsearchEntity> list) throws IOException {
        //创建批量操作的请求
        BulkRequest request = new BulkRequest(indexName);
        //请求加入每个插入数据
        list.forEach(entity -> {
            //每个索引请求，设置id和数据
            request.add(new IndexRequest().id(entity.getId()).source(JSON.toJSONString(entity.getData()),
                    XContentType.JSON));
        });
        //执行批量操作
        client.bulk(request,RequestOptions.DEFAULT);
        log.info("批量插入完成");
    }

    /**
     * 按多条件进行搜索，并实现多列排序
     * @param indexName 索引名称
     * @param from 分页位置
     * @param size 分页长度
     * @param map 条件集合
     * @param sort 排序字段集合
     * @param clazz 对象类型
     * @param <T>
     * @return 分页数据
     */
    public <T> PageEntity<T> searchPageByMap(String indexName, int from, int size, Map<String,String> map,
                                             Map<String,String> sort, Class<T> clazz, String... highlightFields) throws IOException {
        //创建搜索请求对象
        SearchRequest request = new SearchRequest(indexName);
        //创建搜索请求生成器
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //创建布尔查询生成器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //遍历搜索条件，设置搜索的键和值
        if(map != null && map.size() > 0) {
            for (String key : map.keySet()) {
                String value = map.get(key);
                if(!StringUtils.isEmpty(value)){
                    //如果条件不为空，就在布尔查询添加filter，设置短语查询
                    boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery(key,value));
                }
            }
        }
        //如果布尔查询有过滤条件，就设置为搜索条件
        if(boolQueryBuilder.filter().size() > 0){
            builder.query(boolQueryBuilder);
        }
        //设置排序
        if(sort != null && sort.size() > 0){
            //获得排序的字段和方式
            String field = sort.get("field");
            String type = sort.get("type");
            //设置排序字段和方式
            builder.sort(new FieldSortBuilder(field).order(SortOrder.fromString(type)));
        }
        //设置分页
        builder.from(from);
        builder.size(size);
        //完成搜索请求的配置
        request.source(builder);
        //创建高亮生成器
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //设置高亮字段
        for(String field : highlightFields){
            highlightBuilder.field(field);
        }
        //设置前后标签
        highlightBuilder.preTags("<span style='color:red'>").postTags("</span>");
        builder.highlighter(highlightBuilder);
        //发出搜索请求，处理搜索的结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //获得命中结果
        SearchHit[] hits = response.getHits().getHits();
        List<T> results = new ArrayList<>();
        for(SearchHit hit : hits){
            //将结果转换为Java对象
            T obj = JSON.parseObject(hit.getSourceAsString(), clazz);
            results.add(obj);
            //获得所有高亮的字段
            for(String hField : highlightFields){
                //获得反射字段
                try {
                    Field declaredField = clazz.getDeclaredField(hField);
                    //将字段的值替换为带高亮效果的属性值
                    declaredField.setAccessible(true);
                    if(hit.getHighlightFields() != null && hit.getHighlightFields().get(hField) != null){
                        Text[] fragments = hit.getHighlightFields().get(hField).fragments();
                        if(declaredField != null && fragments.length > 0){
                            declaredField.set(obj,fragments[0].string());
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //包装到分页对象中
        PageEntity<T> pageEntity = new PageEntity<>(from,response.getHits().getTotalHits().value,size,results);
        return pageEntity;
    }

    /**
     * 添加和更新
     * @param indexName
     * @param entity
     * @throws IOException
     */
    public void saveOrUpdate(String indexName, ElasticsearchEntity entity) throws IOException {
        //创建索引操作请求
        IndexRequest request = new IndexRequest(indexName);
        //设置一个文档数据
        request.id(entity.getId()).source(JSON.toJSONString(entity.getData()), XContentType.JSON);
        //通过rest客户端发送更新或添加请求
        client.index(request, RequestOptions.DEFAULT);
        log.info("文档保存成功 {}",entity);
    }

    /**
     * 按id删除
     * @param indexName
     * @param id
     * @throws IOException
     */
    public void deleteById(String indexName, String id) throws IOException {
        //创建删除文档请求
        DeleteByQueryRequest request = new DeleteByQueryRequest(indexName);
        //设置删除条件
        request.setQuery(QueryBuilders.termQuery("id",id));
        client.deleteByQuery(request, RequestOptions.DEFAULT);
        log.info("文档删除成功 {}", id);
    }
}
